"""
Fuseki connect -- 用于连接Jena Fuseki服务并对其进行相关操作
@Time: 2020/10/21 8:48
@Author: yb
@File: jena_fuseki.py
"""

import SPARQLWrapper as sw
from SPARQLWrapper import SPARQLWrapper, JSON, POST, DIGEST
from src.spiders.common.rdf_prefix import RdfPrefix, COMMON_PREFIX
from collections import Iterable
from typing import Union
from rdflib import Graph
from deprecated.sphinx import deprecated

from .settings import fuseki_logger


class BaseFuseki:
    """The base class used to connect and operate the Fuseki service."""

    def __init__(self, fuseki_url: str, dataset: str):
        """
        初始化 BaseFusekiConn
        :param fuseki_url: Jena Fuseki 服务的URL，如本地环境下为"http://localhost:3030"
        :param dataset: Jena Fuseki中数据集的名称
        """
        self.fuseki_url = fuseki_url
        self.dataset = dataset
        self.endpoint_url = None
        self.sparql_conn = None

    def run_sparql(self, sparql_str: str):
        """
        运行 SPARQL 语句并获取结果
        :param sparql_str: SPARQL语句
        :return: 运行结果
        """
        pass


class FusekiQuery(BaseFuseki):
    """The class used to connect Fuseki and query data from it."""

    def __init__(self, fuseki_url: str, dataset: str):
        """
        初始化 FusekiQueryConn
        :param fuseki_url: Jena Fuseki 服务的URL，如本地环境下为"http://localhost:3030"
        :param dataset: Jena Fuseki中数据集的名称
        """
        super().__init__(fuseki_url, dataset)
        self.endpoint_url = '/'.join((fuseki_url, dataset, 'query'))
        self.sparql_conn = SPARQLWrapper(self.endpoint_url)

    def run_sparql(self, sparql_str: str, return_format=JSON):
        """
        运行 SPARQL 语句并获取结果，默认返回结果为JSON形式
        :param sparql_str: SPARQL语句
        :param return_format: Possible values are :data:`JSON`, :data:`XML`, :data:`TURTLE`, :data:`N3`, :data:`RDF`, :data:`RDFXML`, :data:`CSV`, :data:`TSV`, :data:`JSONLD` (constants in this module). All other cases are ignored.
        :return: 查询结果
        """
        self.sparql_conn.setQuery(sparql_str)
        self.sparql_conn.setReturnFormat(return_format)
        return self.sparql_conn.query().convert()


class FusekiUpdate(BaseFuseki):
    """The class used to connect Fuseki and update data from it."""

    def __init__(self, fuseki_url: str, dataset: str):
        """
        初始化 FusekiUpdateConn
        :param fuseki_url: Jena Fuseki 服务的URL，如本地环境下为"http://localhost:3030"
        :param dataset: Jena Fuseki中数据集的名称
        """
        super().__init__(fuseki_url, dataset)
        self.endpoint_url = '/'.join((fuseki_url, dataset, 'update'))
        self.sparql_conn = SPARQLWrapper(self.endpoint_url)
        self.sparql_conn.setMethod(POST)
        self.sparql_conn.setHTTPAuth(DIGEST)

    def run_sparql(self, sparql_str: str) -> sw.Wrapper.QueryResult:
        """
        运行 SPARQL 语句并获取结果
        :param sparql_str: SPARQL语句
        :return: 更新结果
        """
        self.sparql_conn.setQuery(sparql_str)
        return self.sparql_conn.query()

    @deprecated(reason="You should use insert_graph function to insert data.")
    def insert_one_record(self, prefixes: Union[None, list[RdfPrefix]], subject: str, predicate: str,
                          object: str) -> sw.Wrapper.QueryResult:
        """
        向 Fuseki 中插入一个RDF三元组
        :param prefixes: 本次插入语句的所有前缀
        :param subject: rdf的subject
        :param predicate: rdf的predicate
        :param object: rdf的object
        :return: Fuseki运行的结果
        :raises: ValueError
        """
        # 检查参数是否正常
        if None in {subject, predicate, object}:
            raise ValueError
        # 将 prefixes 转化成 SPARQL 中的头语句
        if prefixes is not None:
            if not isinstance(prefixes, Iterable):  # 若prefixes既不是None，也不是可迭代对象，则抛出错误
                raise ValueError
        else:
            prefixes = ''
            prefixes = [f'PREFIX {prefix.name}:  {prefix.value}' for prefix in prefixes]
            prefixes = '\n'.join(prefixes)
        # 构造SPARQL语句
        insert_sparql_str = f"""
            PREFIX  : <{COMMON_PREFIX}>
            {prefixes}
            INSERT DATA
            {{
                {subject} {predicate} {object} .
            }}
        """
        print(insert_sparql_str)
        return self.run_sparql(insert_sparql_str)

    @deprecated(reason="You should use insert_graph function to insert data.")
    def insert_records(self, prefixes, subject: str, p_o_list: list[tuple]) -> sw.Wrapper.QueryResult:
        """
        插入一个subject的多组p-o
        :param prefixes: 本次插入语句的所有前缀
        :param subject: rdf的subject
        :param p_o_list: 对应于subject的多组predicate-object
        :return: Fuseki运行的结果
        :raises: ValueError
        """
        # 检查参数是否异常
        if subject is None or p_o_list is None:
            raise ValueError
        # 将 prefixes 转化成 SPARQL 中的头语句
        if prefixes is not None:
            if not isinstance(prefixes, Iterable):  # 若prefixes既不是None，也不是可迭代对象，则抛出错误
                raise ValueError
            prefixes = [f'PREFIX {prefix.name}:  <{prefix.value}>' for prefix in prefixes]
            prefixes = '\n'.join(prefixes)
        # 将 p_o_list 转化成 SPARQL 中的predicate-object语句
        p_o_list = [' '.join(p_o_tuple) for p_o_tuple in p_o_list]
        p_o_str = ';\n'.join(p_o_list)
        # 构造SPARQL语句
        insert_sparql_str = f"""
            PREFIX  : {COMMON_PREFIX}
            {prefixes}
            INSERT DATA
            {{
                {subject}
                    {p_o_str}.
            }}
        """
        return self.run_sparql(insert_sparql_str)

    def insert_graph(self, rdf_graph: Graph) -> sw.Wrapper.QueryResult:
        """
        向Fuseki插入一个RDF Graph中保存的数据
        :param rdf_graph: rdflib.Graph的一个对象，存储了本次insert的所有信息
        :return: Fuseki运行的结果
        :raises: ValueError
        """
        # 检查参数是否异常
        if rdf_graph is None:
            raise ValueError
        # 将 rdf_graph 转化成 SPARQL 的插入语句
        prefix_str = '\n'.join(
            [f'PREFIX {prefix}: <{namespace}>' for (prefix, namespace) in rdf_graph.namespaces()]
        )

        ns_manager = rdf_graph.namespace_manager
        spo_str = '\n'.join(
            [f'{s.n3(ns_manager)} {p.n3(ns_manager)} {o.n3(ns_manager)}.' for (s, p, o) in rdf_graph]
        )

        fuseki_logger.info('准备向Fuseki插入一个Graph对象')
        insert_sparql_str = f"""
            {prefix_str}
            INSERT DATA
            {{
                {spo_str}
            }}
        """
        print(insert_sparql_str)
        return self.run_sparql(insert_sparql_str)
